package com.dangs.rtmp.activity;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;


import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import com.alex.livertmppushsdk.FdkAacEncode;
import com.alex.livertmppushsdk.RtmpSessionManager;
import com.alex.livertmppushsdk.SWVideoEncoder;
import com.dangs.rtmp.L;
import com.dangs.rtmp.R;


import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MainActivity extends AppCompatActivity {
    private int WIDTH_DEF = 640;
    private int HEIGHT_DEF = 480;
    private int FRAMERATE_DEF = 30;
    private int BITRATE_DEF = 1000 * 1000;

    private final int SAMPLE_RATE_DEF = 22050;
    private final int CHANNEL_NUMBER_DEF = 2;

    private final String TAG = "MainActivity";
    private final boolean DEBUG_ENABLE = false;

    private String rtmpUrl = "";

    private PowerManager.WakeLock wakeLock;
    private DataOutputStream _outputStream = null;

    private AudioRecord audioRecord = null;
    private byte[] recorderBuffer = null;
    private FdkAacEncode _fdkaacEnc = null;
    private int fdkaacHandle = 0;

    private ImageView imageView;
    public SurfaceView mSurfaceView = null;
    private Camera mCamera = null;
    private int iDegrees = 0;

    private SWVideoEncoder swVideoEncoder = null;
    private int _iRecorderBufferSize = 0;
    private boolean startFlag = false;
    private int _iCameraCodecType = android.graphics.ImageFormat.NV21;
    private RtmpSessionManager rtmpSessionMgr = null;

    private Queue<byte[]> _YUVQueue = new LinkedList<byte[]>();
    private Lock _yuvQueueLock = new ReentrantLock();
    private Thread h264EncoderThread = null;
    private Thread aacEncoderThread = null;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = getIntent();
        rtmpUrl = intent.getStringExtra(StartActivity.RTMPURL_MESSAGE);
        sendBroadcast(new Intent("com.dangs.smartbox.callmode").putExtra("state", "enter"));
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.INTERNET}, 1001);

        InitAll();
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        wakeLock = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_DIM_WAKE_LOCK, "bright");
    }

    private void InitAll() {
        FrameLayout layout = findViewById(R.id.parentView);
        mSurfaceView = new SurfaceView(this);
        mSurfaceView.getHolder().setFixedSize(WIDTH_DEF, HEIGHT_DEF);
        mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceView.getHolder().setKeepScreenOn(true);
        mSurfaceView.getHolder().addCallback(new SurfaceCallBack());
        mSurfaceView.setZOrderMediaOverlay(false);
        layout.addView(mSurfaceView);
        imageView = new ImageView(this);
        imageView.setBackgroundResource(R.drawable.auto_focus);
        imageView.setAlpha(0.0f);
        layout.addView(imageView);
        InitAudioRecord();
    }


    private void InitAudioRecord() {
        _iRecorderBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE_DEF,
                AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(AudioSource.MIC,
                SAMPLE_RATE_DEF, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, _iRecorderBufferSize);
        recorderBuffer = new byte[_iRecorderBufferSize];

        _fdkaacEnc = new FdkAacEncode();
        fdkaacHandle = _fdkaacEnc.FdkAacInit(SAMPLE_RATE_DEF, CHANNEL_NUMBER_DEF);
    }

    private int getDispalyRotation() {
        int i = getWindowManager().getDefaultDisplay().getRotation();
        switch (i) {
            case Surface.ROTATION_0:
                return 0;
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
        }
        return 0;
    }

    private int getDisplayOritation(int degrees, int cameraId) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int result = 0;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;
        } else {
            result = (info.orientation - degrees + 360) % 360;
        }
        return result;
    }


    private void Start() {
        if (startFlag) {
            return;
        }
        if (DEBUG_ENABLE) {
            File saveDir = Environment.getExternalStorageDirectory();
            String strFilename = saveDir + "/aaa.h264";
            try {
                _outputStream = new DataOutputStream(new FileOutputStream(strFilename));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        rtmpSessionMgr = new RtmpSessionManager();
        rtmpSessionMgr.Start(rtmpUrl);

        int iFormat = _iCameraCodecType;
        swVideoEncoder = new SWVideoEncoder(WIDTH_DEF, HEIGHT_DEF, FRAMERATE_DEF, BITRATE_DEF);
        swVideoEncoder.start(iFormat);
        startFlag = true;
        h264EncoderThread = new Thread(h264Runnable);
        h264EncoderThread.setPriority(Thread.MAX_PRIORITY);
        h264EncoderThread.start();
        audioRecord.startRecording();
        aacEncoderThread = new Thread(aacEncoderRunnable);
        aacEncoderThread.setPriority(Thread.MAX_PRIORITY);
        aacEncoderThread.start();
    }

    private void Stop() {
        startFlag = false;
        aacEncoderThread.interrupt();
        h264EncoderThread.interrupt();
        audioRecord.stop();
        swVideoEncoder.stop();
        rtmpSessionMgr.Stop();
        _yuvQueueLock.lock();
        _YUVQueue.clear();
        _yuvQueueLock.unlock();
        if (DEBUG_ENABLE) {
            if (_outputStream != null) {
                try {
                    _outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    protected void onDestroy() {
        super.onDestroy();
        L.i(TAG, "MainActivity onDestroy...");
        sendBroadcast(new Intent("com.dangs.smartbox.callmode").putExtra("state", "exit"));
    }

    protected void onResume() {
        super.onResume();
        wakeLock.acquire();
    }

    protected void onPause() {
        super.onPause();
        wakeLock.release();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_LEFT:
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                imageView.setAlpha(0.0f);
                break;
            case KeyEvent.KEYCODE_DPAD_RIGHT:
            case KeyEvent.KEYCODE_VOLUME_UP:
                imageView.setAlpha(1.0f);
                break;
            case KeyEvent.KEYCODE_DPAD_UP:
                if (!wakeLock.isHeld()) {
                    wakeLock.acquire();
                }
                break;
            case KeyEvent.KEYCODE_DPAD_DOWN:
                if (wakeLock.isHeld()) {
                    wakeLock.release();
                }
                break;
            case KeyEvent.KEYCODE_BACK:
                if (mCamera != null) {
                    try {
                        mCamera.setPreviewCallback(null);
                        mCamera.setPreviewDisplay(null);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    mCamera.stopPreview();
                    mCamera.release();
                    mCamera = null;
                }
                if (startFlag) {
                    Stop();
                }
                finish();
                break;
        }
        return false;
    }


    private final class SurfaceCallBack implements SurfaceHolder.Callback {
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            L.d(TAG, "surfaceChanged:" + width);
            mCamera.autoFocus((success, camera) -> {
                L.d(TAG, "autoFocus:" + success);
                if (success) {
                    InitCamera();
                }
            });
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            L.d(TAG, "surfaceCreated:");
            iDegrees = getDisplayOritation(getDispalyRotation(), 0);
            if (mCamera == null) {
                mCamera = Camera.open(0);
            }
            InitCamera();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            L.d(TAG, "surfaceDestroyed:");
        }
    }

    public void InitCamera() {
        Camera.Parameters p = mCamera.getParameters();
        List<Size> PreviewSizeList = p.getSupportedPreviewSizes();
        List<Integer> PreviewFormats = p.getSupportedPreviewFormats();
        L.i(TAG, "InitCamera, listing all supported preview sizes");
        for (Camera.Size size : PreviewSizeList) {
            L.i(TAG, "camera width:" + size.width + ", height:" + size.height);
            if (size.width == 1920 && size.height == 1080) {
                WIDTH_DEF = 1920;
                HEIGHT_DEF = 1080;
                break;
            } else if (size.width == 1280 && size.height == 720) {
                WIDTH_DEF = 1280;
                HEIGHT_DEF = 720;
                break;
            }
        }
        L.i(TAG, "width=" + WIDTH_DEF + ", height=" + HEIGHT_DEF);
        Integer iNV21Flag = 0;
        Integer iYV12Flag = 0;
        for (Integer yuvFormat : PreviewFormats) {
            L.i(TAG, "preview formats:" + yuvFormat);
            if (yuvFormat == android.graphics.ImageFormat.YV12) {
                iYV12Flag = android.graphics.ImageFormat.YV12;
            }
            if (yuvFormat == android.graphics.ImageFormat.NV21) {
                iNV21Flag = android.graphics.ImageFormat.NV21;
            }
        }

        if (iNV21Flag != 0) {
            _iCameraCodecType = iNV21Flag;
        } else if (iYV12Flag != 0) {
            _iCameraCodecType = iYV12Flag;
        }
        p.setPreviewSize(WIDTH_DEF, HEIGHT_DEF);
        p.setPreviewFormat(_iCameraCodecType);
        p.setPreviewFrameRate(FRAMERATE_DEF);

        mCamera.setDisplayOrientation(iDegrees);
        p.setRotation(iDegrees);
        mCamera.setPreviewCallback(_previewCallback);
        mCamera.setParameters(p);
        try {
            mCamera.setPreviewDisplay(mSurfaceView.getHolder());
        } catch (Exception e) {
            return;
        }
        mCamera.startPreview();
        mCamera.cancelAutoFocus();
        Start();
    }


    private Camera.PreviewCallback _previewCallback = (YUV, currentCamera) -> {
        if (!startFlag) {
            return;
        }
        byte[] yuv420 = null;
        L.d(TAG, "offer preview, size:" + YUV.length + ", preview size=" + currentCamera.getParameters().get("preview-size"));
        if (_iCameraCodecType == android.graphics.ImageFormat.YV12) {
            yuv420 = new byte[YUV.length];
            swVideoEncoder.swapYV12toI420_Ex(YUV, yuv420, WIDTH_DEF, HEIGHT_DEF);
        } else if (_iCameraCodecType == android.graphics.ImageFormat.NV21) {
            yuv420 = swVideoEncoder.swapNV21toI420(YUV, WIDTH_DEF, HEIGHT_DEF);
        }
        if (yuv420 == null) {
            return;
        }
        L.d(TAG, "offer yuv420, size:" + yuv420.length);
        if (!startFlag) {
            return;
        }
        _yuvQueueLock.lock();
        if (_YUVQueue.size() > 1) {
            _YUVQueue.clear();
        }
        _YUVQueue.offer(yuv420);
        _yuvQueueLock.unlock();
    };


    private Runnable h264Runnable = () -> {
        L.d(TAG, "h264Runnable start");
        while (!h264EncoderThread.interrupted() && startFlag) {
            int iSize = _YUVQueue.size();
            if (iSize > 0) {
                _yuvQueueLock.lock();
                byte[] yuvData = _YUVQueue.poll();
                _yuvQueueLock.unlock();
                if (yuvData == null) {
                    continue;
                }
//                    if (_bIsFront) {
//                        _yuvEdit = _swEncH264.YUV420pRotate270(yuvData, HEIGHT_DEF, WIDTH_DEF);
//                    }
                byte[] h264Data = swVideoEncoder.EncoderH264(yuvData);
                if (h264Data != null) {
                    L.d(TAG, "offer encode view, size:" + h264Data.length);
                    rtmpSessionMgr.InsertVideoData(h264Data);
                    if (DEBUG_ENABLE) {
                        try {
                            _outputStream.write(h264Data);
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    }
                }
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                L.d(TAG, "sleep err:" + e.toString());
            }
        }
        _YUVQueue.clear();
    };


    private Runnable aacEncoderRunnable = () -> {
        DataOutputStream outputStream = null;
        if (DEBUG_ENABLE) {
            File saveDir = Environment.getExternalStorageDirectory();
            String strFilename = saveDir + "/aaa.aac";
            try {
                outputStream = new DataOutputStream(new FileOutputStream(strFilename));
            } catch (FileNotFoundException e1) {
                e1.printStackTrace();
            }
        }

        long lSleepTime = SAMPLE_RATE_DEF * 16 * 2 / recorderBuffer.length;
        while (!aacEncoderThread.interrupted() && startFlag) {
            int iPCMLen = audioRecord.read(recorderBuffer, 0, recorderBuffer.length); // Fill buffer
            if ((iPCMLen != audioRecord.ERROR_BAD_VALUE) && (iPCMLen != 0)) {
                if (fdkaacHandle != 0) {
                    byte[] aacBuffer = _fdkaacEnc.FdkAacEncode(fdkaacHandle, recorderBuffer);
                    if (aacBuffer != null) {
                        long lLen = aacBuffer.length;

                        rtmpSessionMgr.InsertAudioData(aacBuffer);
                        //L.i(LOG_TAG, "fdk aac length="+lLen+" from pcm="+iPCMLen);
                        if (DEBUG_ENABLE) {
                            try {
                                outputStream.write(aacBuffer);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            } else {
                L.i(TAG, "######fail to get PCM data");
            }
            try {
                Thread.sleep(lSleepTime / 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        L.i(TAG, "AAC Encoder Thread ended ......");
    };

}
